package main

import (
	"encoding/binary"
	"errors"
	"fmt"
	"net"
	"net/http"
	"sync"
	"time"

	"github.com/gobwas/ws"
	"github.com/gobwas/ws/wsutil"
)

const (
	CommandPing = 100
	CommandPong = 101
)

func main() {

	server := NewServer("chat", ":8005")
	defer server.Shutdown()

	if err := server.Start(); err != nil {
		panic(err)
	}

}

type Server struct {
	once    sync.Once
	id      string
	address string
	sync.Mutex
	sync.RWMutex
	users map[string]net.Conn
}

func NewServer(id, address string) *Server {
	return newServer(id, address)
}

func newServer(id, address string) *Server {
	return &Server{
		id:      id,
		address: address,
		users:   make(map[string]net.Conn, 100),
	}
}

func (s *Server) Start() error {

	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("http access")
		conn, _, _, err := ws.UpgradeHTTP(r, w)
		if err != nil {
			conn.Close()
			return
		}

		user := r.URL.Query().Get("user")
		if user == "" {
			conn.Close()
			return
		}

		old, ok := s.addUser(user, conn)
		if ok {
			old.Close()
		}
		go func(user string, conn net.Conn) {
			err := s.readloop(user, conn)
			if err != nil {
				fmt.Println(err)
			}

			conn.Close()
			s.delUser(user)
			fmt.Printf("connectin of %s closed", user)
		}(user, conn)

		fmt.Println("start")
	})

	return http.ListenAndServe(s.address, mux)
}
func (s *Server) addUser(user string, conn net.Conn) (net.Conn, bool) {

	s.Mutex.Lock()
	defer s.Mutex.Unlock()
	old, ok := s.users[user]
	s.users[user] = conn
	return old, ok
}

func (s *Server) delUser(user string) {
	s.Mutex.Lock()
	defer s.Mutex.Unlock()
	delete(s.users, user)
}

func (s *Server) Shutdown() {
	s.once.Do(func() {
		s.Mutex.Lock()
		defer s.Mutex.Unlock()
		for _, conn := range s.users {
			conn.Close()
		}
	})
}

func (s *Server) readloop(user string, conn net.Conn) error {
	readwait := time.Minute * 2
	for {
		_ = conn.SetReadDeadline(time.Now().Add(readwait))
		frame, err := ws.ReadFrame(conn)
		if err != nil {
			return err
		}

		if frame.Header.OpCode == ws.OpClose {
			return errors.New("remotee side close the conn")
		}

		if frame.Header.Masked {
			ws.Cipher(frame.Payload, frame.Header.Mask, 0)
		}

		if frame.Header.OpCode == ws.OpText {
			go s.handler(user, string(frame.Payload))
		}

		if frame.Header.OpCode == ws.OpPing {
			_ = wsutil.WriteServerMessage(conn, ws.OpPong, nil)
			continue
		}

		if frame.Header.OpCode == ws.OpBinary {
			go s.handBinary(user, frame.Payload)
		}
	}
}

func (s *Server) handler(user string, message string) {
	s.Mutex.Lock()
	defer s.Mutex.Unlock()
	broadcast := fmt.Sprintf("%s -- from %s", message, user)

	for u, conn := range s.users {
		if u == user {
			continue
		}
		err := s.writeText(conn, broadcast)
		if err != nil {
			fmt.Printf("write to %s failed ,error:%v", user, err)
		}
	}

}

func (s *Server) writeText(conn net.Conn, message string) error {

	f := ws.NewTextFrame([]byte(message))
	return ws.WriteFrame(conn, f)
}

func (s *Server) handBinary(user string, message []byte) {
	fmt.Printf("recv message %v from %s", message, user)

	s.RLock()
	defer s.RUnlock()

	i := 0
	command := binary.BigEndian.Uint16(message[i : i+2])
	i += 2

	payloadLen := binary.BigEndian.Uint32(message[i : i+4])
	fmt.Printf("command:%v payloadlen:%v", command, payloadLen)
	if command == CommandPing {
		u := s.users[user]
		err := wsutil.WriteClientBinary(u, []byte{0, CommandPong, 0, 0, 0, 0})
		if err != nil {
			fmt.Printf("write to %s failed ,err:%v", user, err)
		}
	}

}
